
package sourceFiles;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Random;

import Exceptions.*;
import Exceptions.CentralComputerExceptions.*;
import sourceFiles.GuardianEntry;

/**
 * A class responsible for all Central Computer functionality in E-Park system. 
 * The central computer gets requests from all the components in the system and operate them.
 * The requests can come from: 
 * 1. the Supervisor Station - to add/update the park's rides.
 * 2. the Registration Station - to register new guardians and their children and save their details to 
 * the park system.
 * 3. the User Application - to connect a guardian to the system and buy/cancel rides tickets.
 * 4. the Entrance/Exit from rides station - to check if it's possible to entrance/exit from the ride 
 * and all other relevant parameters. 
 *
 * 
 * @author LASO-Team
 * 
 */

public class CentralComputer implements ICentralComputer {

	private XMLRidesParser parser;
	private HashMap<Integer, Integer> ticketsSold;
	private SupervisorStation supervisorStation;
	static LinkedList<GuardianEntry> guardiansList;
	static LinkedList<ChildEntry> childrenList;
	static LinkedList<IRide> ridesList;
	static LinkedList<RideEntry> ridesCatalog;

	// private LinkedList<IApplication> applicationList;
	// private IApplication application;

	/*--------------------------------------------------------------------------*/
	/**
	 * Central computer c'tor.
	 * @param path
	 * 			the path of the xml input file.
	 * @throws XMLRidesParserException
	 * 			if the xml parser is invalid.
	 * 			
	 */
	public CentralComputer(String path) throws XMLRidesParserException {
		parser = new XMLRidesParser(path);
		ticketsSold = new HashMap<Integer, Integer>();
		supervisorStation = new SupervisorStation(this);
		guardiansList = new LinkedList<GuardianEntry>();
		childrenList = new LinkedList<ChildEntry>();
		ridesList = parser.readRidesFromFile(this);
		ridesCatalog = parser.readRideEntriesFromFile();
		// application = new Application(this);

	}

	/*--------------------------------------------------------------------------*/

	/**
	 * checks if child params are valid.
	 * @param ride
	 * @param child
	 * @return true if the child can enter the ride according to the parameters
	 *         of the child and the ride. throws exception otherwise.
	 * @throws RideIDDoesntExist
	 *             if the ride does not exist
	 * @throws ChildIDDoesntExist
	 *             if the child does not exist
	 * @throws ChildAgeDoesntMatch
	 *             if the child's age does not match
	 * @throws ChildHeightDoesntMatch
	 *             if the child's height does not match
	 */
	private Boolean areChildParamsValid(IRide ride, ChildEntry child)
			throws RideIDDoesntExist, ChildIDDoesntExist, ChildAgeDoesntMatch,
			ChildHeightDoesntMatch {

		// checks if the ride\child in the DB
		if (!ridesList.contains(ride)) {
			throw new RideIDDoesntExist();
		}
		if (!childrenList.contains(child)) {
			throw new ChildIDDoesntExist();
		}

		// checks if the child's compatible with the ride's params
		if (child.getAge() < ride.getMinimumAge()) {
			throw new ChildAgeDoesntMatch();
		}
		if (child.getHeight() < ride.getMinimumHeight()) {
			throw new ChildHeightDoesntMatch();
		}

		return true;
	}

	/**
	 * Finds the ride and return the actual ride.
	 * @param rideID
	 * @return the actual ride by it's ID
	 */
	private IRide findRide(Integer rideID) {
		for (IRide r : ridesList) {
			if (r.getRideID().equals(rideID)) {
				return r;
			}
		}
		return null;
	}

	/**
	 * finds the ride and return the actual ride.
	 * @param rideID
	 * @return the actual ride by it's ID
	 */
	private RideEntry findRideEntry(Integer rideID) {
		for (RideEntry r : ridesCatalog) {
			if (r.getRideID().equals(rideID)) {
				return r;
			}
		}
		return null;
	}

	/**
	 * finds the child and return the actual child by his park ID.
	 * @param childID
	 * @return the actual child's details by his ID
	 */
	private ChildEntry findChild(Integer childID) {
		for (ChildEntry c : childrenList) {
			if (c.getID().equals(childID)) {
				return c;
			}
		}
		return null;
	}

	/**
	 * finds the guardian and return the actual guardian by his  ID.
	 * @param guardianID
	 * @return the actual child's details by his ID
	 */
	private GuardianEntry findGuardian(Integer guardianID) {
		for (GuardianEntry g : guardiansList) {
			if (g.getID().equals(guardianID)) {
				return g;
			}
		}
		return null;
	}

	/**
	 * The method updates the current location of a child.
	 * @param rideID
	 * 			   the ride's ID
	 * @param childID
	 *            Updates the current location of the child and save the new
	 *            rideID
	 * @throws Exceptions.CentralComputerExceptions.ChildIDDoesntExist
	 *             if the child does not exist
	 * @throws Exceptions.CentralComputerExceptions.RideIDDoesntExist
	 *             if the ride does not exist
	 */
	private void UpdateCurrentLocationOfChild(Integer rideID, Integer childID)
			throws ChildIDDoesntExist, RideIDDoesntExist {

		ChildEntry child = findChild(childID);
		IRide ride = findRide(rideID);

		// checks if the child\ride exists.
		if (child == null) {
			throw new ChildIDDoesntExist();
		}
		if (ride == null) {
			throw new RideIDDoesntExist();
		}

		// sets the child's rideID field.
		child.setRideID(rideID);

	}

	/**
	 * adds ticket to ticektSold Map.
	 * @param rideID
	 * 			The ride's ID
	 */
	private void addToTicketsSold(Integer rideID) {
		IRide ride = findRide(rideID);
		if (ride == null) {
			return;
		}
		Integer value;
		if (ticketsSold.containsKey(rideID)) {
			value = ticketsSold.get(rideID);
			value++;
		} else { // the ride didn't have any tickets up till now
			value = 1;
		}
		ticketsSold.put(rideID, value);

	}

	/**
	 * The method removes from ticketSold Map a ticket.
	 * @param rideID
	 */
	private void removeFromTicketsSold(Integer rideID) {
		IRide ride = findRide(rideID);
		if (ride == null) {
			return;
		}

		Integer value = ticketsSold.get(rideID);
		value--;

		if (value == 0) {
			// after the ticket was removed there are no tickets for this ride,
			// so the ride will be removed
			ticketsSold.remove(rideID);
		} else {
			ticketsSold.put(rideID, value);
		}

	}

	/**
	 * The method generates password for guardian user.
	 * @return 
	 * 			generate new random string password
	 */
	private String generatePassword() {
		char[] chars = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
		StringBuilder password = new StringBuilder();
		Random random = new Random();
		for (int i = 0; i < 7; i++) {
			char c = chars[random.nextInt(chars.length)];
			password.append(c);
		}
		String output = password.toString();
		return output;
	}

	
	/**
	 * The method checks the ride params.
	 * @param maxCapacity
	 * 			the max capacity of the ride.
	 * @param minAge
	 * 			the minimal age limitation.
	 * @param minHeight
	 * 			the minimal height limitation.
	 * @throws InvalidCapacity
	 * 			if the capacity is invalid.
	 * @throws InvalidAgeParam
	 * 			if the age limitation is invalid.
	 * @throws InvalidHeightParam
	 * 			if the height limitation is invalid.
	 */
	private void checkRideParams(Integer maxCapacity, Integer minAge,
			Integer minHeight) throws InvalidCapacity, InvalidAgeParam,
			InvalidHeightParam {
		if (maxCapacity < 1) {
			throw new InvalidCapacity();
		}
		if (minAge < 1) {
			throw new InvalidAgeParam();
		}
		if (minHeight < 1) {
			throw new InvalidHeightParam();
		}
	}

	/**
	 * The method generates new ride's ID.
	 * @return 
	 * 		new ride's ID.
	 */
	private Integer generateNewRideId() {
		return ridesList.size() + 1;
	}

	/**
	 * The method generates the price of the ride according to the type of the ride.
	 * @param isExtremeRide
	 * 			remark if the ride is extreme ride or regular ride.
	 * @return
	 * 			the price of the ride.
	 */
	private Double genrateNewRidePrice(Boolean isExtremeRide) {
		if(isExtremeRide){
			return 15.0;
		}
		else {
			return 10.0;
		}
	}

	/*--------------------------------------------------------------------------*/

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#createRide(java.lang.String, java.lang.Boolean, java.lang.Integer, java.lang.Integer, java.lang.Integer)
	 */
	@Override
	public Integer createRide(String rideName, Boolean isExtremeRide,
			Integer maxCapacity, Integer minAge, Integer minHeight)
			throws InvalidRideName, InvalidCapacity, InvalidAgeParam,
			InvalidHeightParam, XMLRidesParserException {

		// params testing
		if (rideName == null) {
			throw new InvalidRideName();
		}
		checkRideParams(maxCapacity, minAge, minHeight);

		// generates new ID for the ride according to it's index in ridesList
		Integer newRideID = generateNewRideId();

		Double newRidePrice = genrateNewRidePrice(isExtremeRide);

		// add new Ride
		Ride newRide = new Ride(newRideID, maxCapacity, minAge, minHeight,
				rideName, this);

		// add new RideEntry
		RideEntry newRideEntry;
		if (isExtremeRide) {
			newRideEntry = new ExtremeRideEntry(rideName, newRideID,
					newRidePrice);
		} else {
			newRideEntry = new RegularRideEntry(rideName, newRideID,
					newRidePrice);
		}

		// add new ride to central computer's lists
		ridesCatalog.add(newRideEntry);
		ridesList.add(newRide);

		// add new ride to XML
		parser.writeNewRideDataToFile(newRide, newRideEntry);
		return newRideID;
	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#updateRide(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
	 */
	@Override
	public void updateRide(Integer rideID, Integer maxCapacity, Integer minAge,
			Integer minHeight) throws InvalidRideId, InvalidCapacity,
			InvalidAgeParam, InvalidHeightParam, RideIDDoesntExist,
			XMLRidesParserException {

		// checks the rideID
		if (rideID < 1) {
			throw new InvalidRideId();
		}

		// checks the ride's params
		checkRideParams(maxCapacity, minAge, minHeight);

		// updates ride in ridesList
		IRide ride = findRide(rideID);
		if (ride == null) {
			throw new RideIDDoesntExist();
		}
		ride.configRideParams(maxCapacity, minAge, minHeight);
		RideEntry entry = findRideEntry(rideID);
		parser.updateRideDataInFile(ride, entry);
	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#addGuardian(java.lang.Integer, java.lang.Integer)
	 */
	@Override
	public String addGuardian(Integer guardianID, Integer billingInfo)
			throws InvalidGuardianID, GuardianAlreadyExist {

		// checks if the guardian ID is valid
		if (guardianID < 1) {
			throw new InvalidGuardianID();
		}

		// checks if the guardian ID already exist
		if (findGuardian(guardianID) != null) {
			throw new GuardianAlreadyExist();
		}

		// generate new password for the guardian
		String password = generatePassword();

		// creates new GuardianEntry
		GuardianEntry guardian = new GuardianEntry(guardianID, password,
				billingInfo);

		// add the new guardian to the guardiansList
		guardiansList.add(guardian);

		// return the new password to the guardianGUI
		return password;
	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#addChildToGuardian(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
	 */
	@Override
	public void addChildToGuardian(Integer guardianID, Integer childID,
			Integer age, Integer height) throws GuardianIDDoesntExist,
			InvalidHeightParam, InvalidAgeParam, ChildAlreadyExist,
			InvalidGuardianID, InvalidChildID {

		// checks if the guardian ID is valid
		if (guardianID < 1) {
			throw new InvalidGuardianID();
		}

		// checks if the guardian ID already exist
		if (findGuardian(guardianID) == null) {
			throw new GuardianIDDoesntExist();
		}

		// checks if the child ID is valid
		if (childID < 1) {
			throw new InvalidChildID();
		}

		// checks if the child ID already exist
		if (findChild(childID) != null) {
			throw new ChildAlreadyExist();
		}

		// checks if the child age is valid
		if (age < 1) {
			throw new InvalidAgeParam();
		}

		// checks if the child height is valid
		if (height < 1) {
			throw new InvalidHeightParam();
		}

		// creates new child
		ChildEntry child = new ChildEntry(childID, age, height);

		// add the new child to the childrenList in the central computer DB
		childrenList.add(child);

		// add the child to the guardian's childrenList
		GuardianEntry guardian = findGuardian(guardianID);
		guardian.getChildrenList().add(child);

	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#connectGuardian(java.lang.Integer, java.lang.String)
	 */
	@Override
	public void connectGuardian(Integer userID, String userPassword)
			throws InvalidPassword, GuardianIDDoesntExist,
			ErrorInGuardianPassword, InvalidGuardianID {

		// checks if the guardian ID is valid
		if (userID < 1) {
			throw new InvalidGuardianID();
		}

		if (userPassword == null) {
			throw new InvalidPassword();
		}

		GuardianEntry guardian = findGuardian(userID);

		if (guardian == null) {
			throw new GuardianIDDoesntExist(); // guardian was not found
		}
		if (!guardian.getPassword().equals(userPassword)) {
			throw new ErrorInGuardianPassword(); // guardian was found but
													// password doesn't match
		}


	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#buyingProcess(java.lang.Integer, java.lang.Integer, java.lang.Integer)
	 */
	@Override
	public void buyingProcess(Integer rideID, Integer guardianID,
			Integer childID) throws InvalidRideId, ChildIDDoesntExist,
			RideIDDoesntExist, GuardianIDDoesntExist, InvalidChildID,
			InvalidGuardianID {

		// checks the rideID
		if (rideID < 1) {
			throw new InvalidRideId();
		}

		RideEntry ride = findRideEntry(rideID);
		if (ride == null) {
			throw new RideIDDoesntExist();
		}

		// checks if the guardian ID is valid
		if (guardianID < 1) {
			throw new InvalidGuardianID();
		}

		// checks if the guardian doesn't exist
		if (findGuardian(guardianID) == null) {
			throw new GuardianIDDoesntExist();
		}

		// checks if the child ID is valid
		if (childID < 1) {
			throw new InvalidChildID();
		}

		ChildEntry child = findChild(childID);

		// checks if the child ID not exist
		if (child == null) {
			throw new ChildIDDoesntExist();
		}

		// checks if the ride is extreme or regular
		boolean isExtreme = false;

		if (ride.getClass().toString()
				.equals(ExtremeRideEntry.class.toString())) {
			isExtreme = true;
		}

		// add ticket to child E-ticket
		child.addTicket(rideID, isExtreme);

		// add the acquisition to the DB
		addToTicketsSold(rideID);

		// process failed.

	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#performBilling(java.lang.Double, java.lang.Integer)
	 */
	@Override
	public void performBilling(Double ridePrice, Integer billingInfo) {

	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#cancelAcquisition(java.lang.Integer, java.lang.Integer, java.lang.Integer)
	 */
	@Override
	public void cancelAcquisition(Integer rideID, Integer guardianID,
			Integer childID) throws InvalidRideId, ChildIDDoesntExist,
			RideIDDoesntExist, TicketDoesntExist, GuardianIDDoesntExist,
			InvalidGuardianID, InvalidChildID {

		// checks the rideID
		if (rideID < 1) {
			throw new InvalidRideId();
		}

		RideEntry ride = findRideEntry(rideID);
		if (ride == null) {
			throw new RideIDDoesntExist();
		}

		// checks if the guardian ID is valid
		if (guardianID < 1) {
			throw new InvalidGuardianID();
		}

		// checks if the guardian ID already exist
		if (findGuardian(guardianID) == null) {
			throw new GuardianIDDoesntExist();
		}

		// checks if the child ID is valid
		if (childID < 1) {
			throw new InvalidChildID();
		}

		ChildEntry child = findChild(childID);

		// checks if the child ID not exist
		if (child == null) {
			throw new ChildIDDoesntExist();
		}

		// checks if the ride is extreme or regular
		boolean isExtreme = false;

		if (ride.getClass().toString()
				.equals(ExtremeRideEntry.class.toString())) {
			isExtreme = true;
		}

		// remove ticket to child E-ticket
		child.removeTicket(rideID, isExtreme);

		// remove the acquisition from the DB
		removeFromTicketsSold(rideID);

	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#validateChildForRide(java.lang.Integer, java.lang.Integer)
	 */
	@Override
	public void validateChildForRide(Integer rideID, Integer childID)
			throws RideIDDoesntExist, ChildIDDoesntExist, ChildAgeDoesntMatch,
			ChildHeightDoesntMatch, TicketDoesntExist, InvalidRideId,
			InvalidChildID {

		// checks if the ride ID is valid
		if (rideID < 1) {
			throw new InvalidRideId();
		}

		// checks if the child ID is valid
		if (childID < 1) {
			throw new InvalidChildID();
		}

		// finds the ride
		IRide ride = findRide(rideID);
		RideEntry rideEntry = findRideEntry(rideID);
		if (ride == null || rideEntry == null) {
			throw new RideIDDoesntExist();
		}

		// finds the child
		ChildEntry child = findChild(childID);
		if (child == null) {
			throw new ChildIDDoesntExist();
		}

//		try {
			// checks if the params of the child are valid
			areChildParamsValid(ride, child);

			// checks if the ride is extreme or regular
			boolean isExtreme = false;

			if (rideEntry.getClass().toString()
					.equals(ExtremeRideEntry.class.toString())) {
				isExtreme = true;
			}

			// remove the ticket from child E-ticket
			child.removeTicket(rideID, isExtreme);

			// updates the current location of the child
			UpdateCurrentLocationOfChild(rideID, childID);

//		} catch (RideIDDoesntExist e) {
//			throw new RideIDDoesntExist();
//		} catch (ChildIDDoesntExist e) {
//			throw new ChildIDDoesntExist();
//		} catch (ChildAgeDoesntMatch e) {
//			throw new ChildAgeDoesntMatch();
//		} catch (ChildHeightDoesntMatch e) {
//			throw new ChildHeightDoesntMatch();
//		}

	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#retrieveChildrenLocation(java.lang.Integer)
	 */
	@Override
	public Map<Integer, Integer> retrieveChildrenLocation(Integer guardianID)
			throws GuardianIDDoesntExist, NoChildrenForGuardian, InvalidGuardianID {

		// checks if the guardian ID is valid
		if (guardianID < 1) {
			throw new InvalidGuardianID();
		}
		
		// checks if the guardian exist in the central computer's DB
		GuardianEntry guardian = findGuardian(guardianID);
		if (guardian == null) {
			throw new GuardianIDDoesntExist();
		}

		// creates the new map result- key is childID, value is rideID
		Map<Integer, Integer> tempResult = new HashMap<Integer, Integer>();

		LinkedList<ChildEntry> children = guardian.getChildrenList();
		// checks if there is no children for this guardian
		if (children.size() == 0 ) {
			throw new NoChildrenForGuardian();
		}

		for (ChildEntry c : children) {
			tempResult.put(c.getID(), c.getRideID());
		}
		Map<Integer, Integer> result = new HashMap<Integer, Integer>();

		
		 for (Map.Entry<Integer, Integer> entry : tempResult.entrySet()) {
		    if( entry.getValue() != 0 ){
		    	result.put(entry.getKey(), entry.getValue());
		    }
		}
		
		
		return result;
	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#retrieveChildETicket(java.lang.Integer)
	 */
	@Override
	public ETicket retrieveChildETicket(Integer childID)
			throws ChildIDDoesntExist, InvalidChildID {

		// checks if the child ID is valid
		if (childID < 1) {
			throw new InvalidChildID();
		}

		// checks if the child exist in the central computer's DB
		ChildEntry child = findChild(childID);
		if (child == null) {
			throw new ChildIDDoesntExist();
		}

		// return the child's E-Ticket
		return child.getChildsTicket();
	}

	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#getParser()
	 */
	public XMLRidesParser getParser() {
		return parser;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#setParser(sourceFiles.XMLRidesParser)
	 */
	public void setParser(XMLRidesParser parser) {
		this.parser = parser;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#getTicketsSold()
	 */
	public HashMap<Integer, Integer> getTicketsSold() {
		return ticketsSold;
	}

	
	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#setTicketsSold(java.util.HashMap)
	 */
	public void setTicketsSold(HashMap<Integer, Integer> ticketsSold) {
		this.ticketsSold = ticketsSold;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#getSupervisorStation()
	 */
	public SupervisorStation getSupervisorStation() {
		return supervisorStation;
	}

	
	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#setSupervisorStation(sourceFiles.SupervisorStation)
	 */
	public void setSupervisorStation(SupervisorStation supervisorStation) {
		this.supervisorStation = supervisorStation;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#getGuardiansList()
	 */
	public LinkedList<GuardianEntry> getGuardiansList() {
		return guardiansList;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#setGuardiansList(java.util.LinkedList)
	 */
	public void setGuardiansList(LinkedList<GuardianEntry> guardiansList) {
		CentralComputer.guardiansList = guardiansList;
	}

	
	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#getChildrenList()
	 */
	public LinkedList<ChildEntry> getChildrenList() {
		return childrenList;
	}

	
	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#setChildrenList(java.util.LinkedList)
	 */
	public void setChildrenList(LinkedList<ChildEntry> childrenList) {
		CentralComputer.childrenList = childrenList;
	}

	
	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#getRidesList()
	 */
	public LinkedList<IRide> getRidesList() {
		return ridesList;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#setRidesList(java.util.LinkedList)
	 */
	public void setRidesList(LinkedList<IRide> ridesList) {
		CentralComputer.ridesList = ridesList;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#getRidesCatalog()
	 */
	public LinkedList<RideEntry> getRidesCatalog() {
		return ridesCatalog;
	}


	/* (non-Javadoc)
	 * @see sourceFiles.ICentralComputer#setRidesCatalog(java.util.LinkedList)
	 */
	public void setRidesCatalog(LinkedList<RideEntry> ridesCatalog) {
		CentralComputer.ridesCatalog = ridesCatalog;
	}

	// @Override
	// public void childLeavesRide(Integer childID) {

	// }
}
